{% extends "console/layout.html" %}
{% block title %}Extensions · {{ site_name | default('RustPBX') }}{% endblock %}
{% block content %}
<div class="p-4 sm:p-6" x-data='extensionsPage({
    basePath: {{ (base_path | default("/console")) | tojson }},
    filters: {{ filters | default({}) | tojson }},
    createUrl: {{ create_url | tojson }}
})' x-init="init()">
    <div class="mx-auto max-w-7xl space-y-6">
        <div class="flex flex-col gap-3 lg:flex-row lg:items-end lg:justify-between">
            <div class="space-y-0.5">
                <p class="text-[11px] font-semibold uppercase tracking-wide text-sky-600">Directory management</p>
                <h1 class="text-2xl font-semibold text-slate-900">Extensions</h1>
                <p class="text-sm text-slate-500 sm:max-w-xl">Review, search, and manage extension endpoints without
                    refreshing the page.</p>
            </div>
            <div class="flex gap-3">
                <a :href="createUrl"
                    class="inline-flex items-center gap-2 rounded-lg bg-sky-600 px-4 py-2 text-sm font-semibold text-white shadow-sm transition hover:bg-sky-500 focus:outline-none focus:ring-2 focus:ring-sky-400 focus:ring-offset-2">
                    <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.8">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M10 4v12m6-6H4" />
                    </svg>
                    New extension
                </a>
            </div>
        </div>

        <template x-if="flash">
            <div class="rounded-lg border border-emerald-200 bg-emerald-50 px-4 py-3 text-sm text-emerald-700"
                x-text="flash"></div>
        </template>
        <template x-if="error">
            <div class="rounded-lg border border-rose-200 bg-rose-50 px-4 py-3 text-sm text-rose-700" x-text="error">
            </div>
        </template>

        <section class="rounded-xl bg-white p-4 sm:p-5 lg:p-6 shadow-sm ring-1 ring-black/5">
            <div class="flex flex-wrap items-center justify-between gap-2">
                <div class="space-y-0.5">
                    <h2 class="text-sm font-semibold text-slate-900">Filters</h2>
                </div>
                <button type="button"
                    class="inline-flex items-center gap-2 rounded-lg border border-slate-200 px-3 py-1.5 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700"
                    @click="resetFilters()">
                    <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M10 3v14m-7-7h14" />
                    </svg>
                    Clear all
                </button>
            </div>

            <div class="mt-4 grid gap-4 md:grid-cols-4">
                <label class="flex flex-col gap-2 text-sm font-medium text-slate-700">
                    Search
                    <div class="relative">
                        <svg class="pointer-events-none absolute left-3 top-1/2 h-4 w-4 -translate-y-1/2 text-slate-400"
                            viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="1.6">
                            <path stroke-linecap="round" stroke-linejoin="round"
                                d="m17.5 17.5-3.65-3.65m0 0a5.5 5.5 0 1 0-7.778-7.778 5.5 5.5 0 0 0 7.778 7.778Z" />
                        </svg>
                        <input type="search"
                            class="w-full rounded-lg border border-slate-200 px-3 py-2 pl-9 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                            placeholder="Extension, display name, or email" x-model="keyword"
                            @input.debounce.400ms="applyFilters()">
                    </div>
                </label>

                <div>
                    <div class="text-sm font-medium text-slate-700">Departments</div>
                    <div class="mt-2 flex flex-wrap gap-2">
                        <template x-if="!departments.length">
                            <span class="rounded-full border border-slate-200 px-3 py-1 text-xs text-slate-400">No
                                departments configured.</span>
                        </template>
                        <template x-for="dept in departments" :key="dept.id">
                            <label
                                class="inline-flex items-center gap-2 rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold text-slate-600 transition hover:border-sky-300 hover:text-sky-700">
                                <input type="checkbox"
                                    class="h-4 w-4 rounded border-slate-300 text-sky-600 focus:ring-sky-400"
                                    :value="String(dept.id)" x-model="selectedDepartments" @change="applyFilters()">
                                <span x-text="dept.name"></span>
                            </label>
                        </template>
                    </div>
                </div>
            </div>

            <div class="mt-4 grid gap-4 md:grid-cols-3 lg:grid-cols-4">
                <label class="flex flex-col gap-2 text-sm font-medium text-slate-700">
                    Call forwarding
                    <select x-model="callForwardingFilter" @change="applyFilters()"
                        class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                        <option value="any">Any</option>
                        <option value="enabled">Enabled</option>
                        <option value="disabled">Disabled</option>
                    </select>
                </label>

                <label class="flex flex-col gap-2 text-sm font-medium text-slate-700">
                    Extension status
                    <select x-model="statusFilter" @change="applyFilters()"
                        class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                        <option value="">Any</option>
                        <template x-for="status in statuses" :key="status">
                            <option :value="status" x-text="status"></option>
                        </template>
                    </select>
                </label>

                <label class="flex flex-col gap-2 text-sm font-medium text-slate-700">
                    Login capability
                    <select x-model="loginFilter" @change="applyFilters()"
                        class="rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                        <option value="any">Any</option>
                        <option value="allowed">Allowed</option>
                        <option value="disabled">Disabled</option>
                    </select>
                </label>

                <div
                    class="flex flex-col gap-3 text-sm font-medium text-slate-700 md:col-span-2 md:flex-row md:items-end md:gap-4">
                    <div class="flex flex-1 flex-col gap-2">
                        <span>Created between</span>
                        <div class="grid gap-2 sm:grid-cols-2">
                            <input type="datetime-local" x-model="createdFrom" @change="applyFilters()"
                                class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <input type="datetime-local" x-model="createdTo" @change="applyFilters()"
                                class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                        </div>
                    </div>
                    <div class="flex flex-1 flex-col gap-2">
                        <span>Registered between</span>
                        <div class="grid gap-2 sm:grid-cols-2">
                            <input type="datetime-local" x-model="registeredFrom" @change="applyFilters()"
                                class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                            <input type="datetime-local" x-model="registeredTo" @change="applyFilters()"
                                class="w-full rounded-lg border border-slate-200 px-3 py-2 text-sm text-slate-700 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200">
                        </div>
                    </div>
                </div>
            </div>
        </section>

        <section class="overflow-hidden rounded-xl bg-white ring-1 ring-black/5">
            <div class="overflow-x-auto">
                <table class="min-w-full divide-y divide-slate-200 text-left text-sm">
                    <thead class="bg-slate-50 text-xs font-semibold uppercase tracking-wide text-slate-500">
                        <tr>
                            <th scope="col" class="px-4 py-2">Extension</th>
                            <th scope="col" class="px-4 py-2">Departments</th>
                            <th scope="col" class="px-4 py-2">Status</th>
                            <th scope="col" class="px-4 py-2">Login</th>
                            <th scope="col" class="px-4 py-2">Public Access</th>
                            <th scope="col" class="px-4 py-2">Forwarding</th>
                            <th scope="col" class="px-4 py-2">Created</th>
                            <th scope="col" class="px-4 py-2">Registered</th>
                            <th scope="col" class="px-4 py-2">Actions</th>
                        </tr>
                    </thead>
                    <tbody class="divide-y divide-slate-100 bg-white text-sm text-slate-600">
                        <template x-if="loading">
                            <tr>
                                <td colspan="9" class="px-4 py-6 text-center text-sm text-slate-500">
                                    <div class="inline-flex items-center gap-2">
                                        <svg class="h-4 w-4 animate-spin text-slate-400" viewBox="0 0 24 24" fill="none"
                                            stroke="currentColor" stroke-width="2">
                                            <path stroke-linecap="round" stroke-linejoin="round"
                                                d="M12 3v3m6.364 1.636-2.121 2.121M21 12h-3m-1.636 6.364-2.121-2.121M12 21v-3m-6.364-1.636 2.121-2.121M3 12h3m1.636-6.364 2.121 2.121" />
                                        </svg>
                                        Loading extensions…
                                    </div>
                                </td>
                            </tr>
                        </template>
                        <template x-if="!loading && !extensions.length">
                            <tr>
                                <td colspan="9" class="px-4 py-6 text-center text-sm text-slate-500">
                                    No extensions found for the current filters.
                                </td>
                            </tr>
                        </template>
                        <template x-for="extension in extensions" :key="extension.id">
                            <tr class="hover:bg-slate-50">
                                <td class="whitespace-nowrap px-4 py-2">
                                    <div class="font-medium text-slate-900"
                                        x-text="formatDefault(extension.display_name, '—')"></div>
                                    <div class="text-xs text-slate-400">Ext <span x-text="extension.extension"></span>
                                    </div>
                                    <template x-if="extension.email">
                                        <div class="text-xs text-slate-400" x-text="extension.email"></div>
                                    </template>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2">
                                    <template x-if="resolveDepartments(extension).length">
                                        <div class="flex flex-wrap gap-1">
                                            <template x-for="dept in resolveDepartments(extension)" :key="dept">
                                                <span
                                                    class="inline-flex items-center rounded-full bg-slate-100 px-2 py-0.5 text-xs font-medium text-slate-700 ring-1 ring-slate-200"
                                                    x-text="dept"></span>
                                            </template>
                                        </div>
                                    </template>
                                    <template x-if="!resolveDepartments(extension).length">
                                        <span class="text-slate-400">—</span>
                                    </template>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2">
                                    <span
                                        class="inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-semibold"
                                        :class="statusMeta(extension.status).badge">
                                        <span class="h-1.5 w-1.5 rounded-full"
                                            :class="statusMeta(extension.status).dot"></span>
                                        <span x-text="statusMeta(extension.status).label"></span>
                                    </span>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2">
                                    <span
                                        class="inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ring-1"
                                        :class="loginAllowed(extension) ? 'bg-emerald-50 text-emerald-600 ring-emerald-100' : 'bg-rose-50 text-rose-600 ring-rose-100'">
                                        <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.8">
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M5 10l4 4 6-8"
                                                x-show="loginAllowed(extension)"></path>
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M6 6l8 8M6 14l8-8"
                                                x-show="!loginAllowed(extension)"></path>
                                        </svg>
                                        <span x-text="loginAllowed(extension) ? 'Enabled' : 'Disabled'"></span>
                                    </span>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2">
                                    <span
                                        class="inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ring-1"
                                        :class="publicCallsAllowed(extension) ? 'bg-sky-50 text-sky-600 ring-sky-100' : 'bg-slate-100 text-slate-600 ring-slate-200'">
                                        <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.8">
                                            <path stroke-linecap="round" stroke-linejoin="round"
                                                d="M4.75 3.75h2.5l1 3-1.5.75a7 7 0 0 0 4.5 4.5l.75-1.5 3 1v2.5a1 1 0 0 1-1 1c-6.351 0-11.5-5.149-11.5-11.5a1 1 0 0 1 1-1Z" />
                                        </svg>
                                        <span x-text="publicCallsAllowed(extension) ? 'Allowed' : 'Restricted'"></span>
                                    </span>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2">
                                    <span
                                        class="inline-flex items-center gap-1 rounded-full px-2.5 py-0.5 text-xs font-medium ring-1"
                                        :class="forwardingLabel(extension) === 'None' ? 'bg-slate-100 text-slate-600 ring-slate-200' : 'bg-sky-50 text-sky-600 ring-sky-100'">
                                        <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                            stroke-width="1.8">
                                            <path stroke-linecap="round" stroke-linejoin="round" d="M7 5l6 5-6 5" />
                                        </svg>
                                        <span x-text="forwardingLabel(extension)"></span>
                                    </span>
                                    <template x-if="forwardingDestination(extension)">
                                        <div class="mt-1 max-w-[12rem] truncate text-xs text-slate-400"
                                            :title="forwardingDestination(extension)"
                                            x-text="forwardingDestination(extension)"></div>
                                    </template>
                                </td>
                                <td class="whitespace-nowrap px-4 py-2 text-slate-500"
                                    x-text="formatDate(extension.created_at)"></td>
                                <td class="whitespace-nowrap px-4 py-2 text-slate-500">
                                    <template x-if="registrationError(extension)">
                                        <div
                                            class="rounded-md border border-rose-200 bg-rose-50 px-3 py-2 text-xs text-rose-600">
                                            <div class="font-semibold">Locator error</div>
                                            <div x-text="registrationError(extension)"></div>
                                        </div>
                                    </template>
                                    <template x-if="!registrationError(extension) && hasActiveRegistration(extension)">
                                        <div class="flex items-start gap-2">
                                            <span
                                                class="mt-0.5 inline-flex h-4 w-4 items-center justify-center rounded-full bg-emerald-100 text-emerald-600 ring-1 ring-emerald-200">
                                                <svg class="h-3 w-3" viewBox="0 0 20 20" fill="none"
                                                    stroke="currentColor" stroke-width="2">
                                                    <path stroke-linecap="round" stroke-linejoin="round"
                                                        d="M5 10l3 3 7-7" />
                                                </svg>
                                            </span>
                                            <div class="leading-tight">
                                                <div class="text-xs font-semibold text-slate-700"
                                                    x-text="registrationDevice(extension)"></div>
                                                <div class="text-xs text-slate-400"
                                                    x-text="registrationStatus(extension)"></div>
                                            </div>
                                        </div>
                                    </template>
                                    <template x-if="!registrationError(extension) && !hasActiveRegistration(extension)">
                                        <div class="flex items-start gap-2 text-slate-400">
                                            <span
                                                class="mt-0.5 inline-flex h-4 w-4 items-center justify-center rounded-full bg-slate-100 text-slate-500 ring-1 ring-slate-200">
                                                <svg class="h-3 w-3" viewBox="0 0 20 20" fill="none"
                                                    stroke="currentColor" stroke-width="2">
                                                    <path stroke-linecap="round" stroke-linejoin="round"
                                                        d="M6 6l8 8M6 14l8-8" />
                                                </svg>
                                            </span>
                                            <div class="leading-tight">
                                                <div class="text-xs font-semibold"
                                                    x-text="registrationStatus(extension)"></div>
                                            </div>
                                        </div>
                                    </template>
                                </td>
                                <td class="px-4 py-2">
                                    <div class="flex items-center gap-2">
                                        <a :href="detailUrl(extension.id)" title="Edit extension"
                                            class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-500 transition hover:border-sky-300 hover:text-sky-600">
                                            <span class="sr-only">Edit</span>
                                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                                stroke-width="1.6">
                                                <path stroke-linecap="round" stroke-linejoin="round"
                                                    d="M13.5 3.5l3 3-8 8H5.5v-3l8-8z" />
                                            </svg>
                                        </a>
                                        <button type="button" title="Toggle login"
                                            class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-500 transition hover:border-amber-300 hover:text-amber-600"
                                            :disabled="processing.toggle === extension.id"
                                            @click="toggleLogin(extension)">
                                            <span class="sr-only">Toggle login</span>
                                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                                stroke-width="1.6">
                                                <path stroke-linecap="round" stroke-linejoin="round"
                                                    d="M9 12.75 11.25 15 15 9.75m-3-7.036A11.959 11.959 0 0 1 3.598 6 11.99 11.99 0 0 0 3 9.749c0 5.592 3.824 10.29 9 11.623 5.176-1.332 9-6.03 9-11.622 0-1.31-.21-2.571-.598-3.751h-.152c-3.196 0-6.1-1.248-8.25-3.285Z" />
                                            </svg>
                                        </button>
                                        <button type="button" title="Delete extension"
                                            class="inline-flex items-center justify-center rounded-full border border-slate-200 p-2 text-slate-500 transition hover:border-rose-300 hover:text-rose-600"
                                            :disabled="processing.delete === extension.id"
                                            @click="confirmDelete(extension)">
                                            <span class="sr-only">Delete</span>
                                            <svg class="h-4 w-4" viewBox="0 0 20 20" fill="none" stroke="currentColor"
                                                stroke-width="1.6">
                                                <path stroke-linecap="round" stroke-linejoin="round"
                                                    d="M6 7h8m-7 0v8m3-8v8m3-8v8M4 7h12M8 4h4a1 1 0 011 1v2H7V5a1 1 0 011-1z" />
                                            </svg>
                                        </button>
                                    </div>
                                </td>
                            </tr>
                        </template>
                    </tbody>
                </table>
            </div>
        </section>

        <section
            class="flex flex-col gap-3 rounded-xl border border-slate-100 bg-white px-4 py-4 text-xs text-slate-500 sm:flex-row sm:items-center sm:justify-between"
            x-show="pagination">
            <div>
                Showing
                <span class="font-semibold text-slate-700" x-text="pagination?.showing_from ?? 0"></span>
                –
                <span class="font-semibold text-slate-700" x-text="pagination?.showing_to ?? 0"></span>
                of
                <span class="font-semibold text-slate-700" x-text="pagination?.total_items ?? 0"></span>
                extensions
            </div>
            <div class="flex items-center gap-2">
                <label class="hidden md:inline-flex items-center gap-1 text-slate-400">
                    Per page
                    <select
                        class="rounded-md border border-slate-200 px-2 py-1 text-xs text-slate-600 focus:border-sky-300 focus:outline-none focus:ring-2 focus:ring-sky-200"
                        @change="perPage = parseInt($event.target.value, 10) || 20; applyFilters();" :value="perPage">
                        <option value="10">10</option>
                        <option value="20">20</option>
                        <option value="50">50</option>
                    </select>
                </label>
                <button type="button"
                    class="inline-flex items-center gap-1 rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold hover:border-sky-300 hover:text-sky-700"
                    :class="{ 'pointer-events-none opacity-40': !(pagination?.has_prev) }" @click="prevPage()">
                    <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M12 5l-4 5 4 5" />
                    </svg>
                    Prev
                </button>
                <span class="rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold text-slate-600">
                    Page <span x-text="pagination?.current_page ?? page"></span> /
                    <span x-text="pagination?.total_pages ?? 1"></span>
                </span>
                <button type="button"
                    class="inline-flex items-center gap-1 rounded-full border border-slate-200 px-3 py-1 text-xs font-semibold hover:border-sky-300 hover:text-sky-700"
                    :class="{ 'pointer-events-none opacity-40': !(pagination?.has_next) }" @click="nextPage()">
                    Next
                    <svg class="h-3.5 w-3.5" viewBox="0 0 20 20" fill="none" stroke="currentColor" stroke-width="2">
                        <path stroke-linecap="round" stroke-linejoin="round" d="M8 5l4 5-4 5" />
                    </svg>
                </button>
            </div>
        </section>
    </div>
</div>

<script>
    document.addEventListener('alpine:init', () => {
        Alpine.data('extensionsPage', (options) => {
            const normalizeLocator = (raw) => {
                if (!raw || typeof raw !== 'object') {
                    return {
                        available: false,
                        query_uri: null,
                        realm: null,
                        total: 0,
                        records: [],
                        error: null,
                    };
                }
                return {
                    available: !!raw.available,
                    query_uri: raw.query_uri || null,
                    realm: raw.realm || null,
                    total: Number(raw.total) || 0,
                    records: Array.isArray(raw.records) ? raw.records : [],
                    error: raw.error || null,
                };
            };

            return {
                basePath: options.basePath || '/console',
                listEndpoint: `${options.basePath || '/console'}/extensions`,
                createUrl: options.createUrl || `${options.basePath || '/console'}/extensions/new`,
                filtersRaw: options.filters || {},
                departments: [],
                statuses: [],
                extensions: [],
                pagination: null,
                loading: false,
                error: null,
                flash: null,
                keyword: '',
                selectedDepartments: [],
                callForwardingFilter: 'any',
                statusFilter: '',
                loginFilter: 'any',
                createdFrom: '',
                createdTo: '',
                registeredFrom: '',
                registeredTo: '',
                page: 1,
                perPage: 20,
                processing: {
                    toggle: null,
                    delete: null,
                },
                init() {
                    this.departments = (this.filtersRaw.departments || []).map((item) => ({
                        id: item.id,
                        name: item.name || item.display_name || item.label || `Department #${item.id}`,
                    }));
                    this.statuses = (this.filtersRaw.statuses || [])
                        .map((value) => (typeof value === 'string' ? value : String(value || '')))
                        .map((value) => value.trim())
                        .filter((value, index, self) => value.length > 0 && self.indexOf(value) === index)
                        .sort((a, b) => a.localeCompare(b));
                    this.fetchExtensions();
                },
                resetFilters() {
                    this.keyword = '';
                    this.selectedDepartments = [];
                    this.callForwardingFilter = 'any';
                    this.statusFilter = '';
                    this.loginFilter = 'any';
                    this.createdFrom = '';
                    this.createdTo = '';
                    this.registeredFrom = '';
                    this.registeredTo = '';
                    this.applyFilters();
                },
                buildParams() {
                    let department_ids = (this.selectedDepartments || [])
                        .map((id) => parseInt(id, 10))
                        .filter((id) => Number.isFinite(id));
                    const callForwarding = this.callForwardingFilter === 'enabled'
                        ? true
                        : this.callForwardingFilter === 'disabled'
                            ? false
                            : undefined;
                    const loginAllowed = this.loginFilter === 'allowed'
                        ? true
                        : this.loginFilter === 'disabled'
                            ? false
                            : undefined;
                    return {
                        page: this.page,
                        per_page: this.perPage,
                        filters: {
                            q: this.keyword.trim() || undefined,
                            department_ids,
                            call_forwarding_enabled: callForwarding,
                            status: this.statusFilter || undefined,
                            login_allowed: loginAllowed,
                            created_at_from: this.createdFrom || undefined,
                            created_at_to: this.createdTo || undefined,
                            registered_at_from: this.registeredFrom || undefined,
                            registered_at_to: this.registeredTo || undefined,
                        },
                    }
                },
                async fetchExtensions() {
                    this.loading = true;
                    this.error = null;
                    try {
                        const params = this.buildParams();
                        const response = await fetch(`${this.listEndpoint}`, {
                            method: 'POST',
                            headers: {
                                'Content-Type': 'application/json',
                            },
                            body: JSON.stringify(params),
                        });
                        const data = await response.json();
                        if (!response.ok) {
                            throw new Error(data?.message || 'Failed to load extensions');
                        }

                        const items = Array.isArray(data?.items) ? data.items : [];
                        const normalizedExtensions = items.map((item) => {
                            const base = (item && typeof item === 'object' ? item.extension : null) || {};
                            const departments = Array.isArray(item?.departments) ? item.departments : [];
                            const registrations = normalizeLocator(item?.registrations);
                            return {
                                ...base,
                                departments,
                                registrations,
                            };
                        });

                        const perPageRaw = Number(data?.per_page);
                        const currentPageRaw = Number(data?.page);
                        const totalItemsRaw = Number(data?.total_items);
                        const totalPagesRaw = Number(data?.total_pages);

                        const perPage = Number.isFinite(perPageRaw) && perPageRaw > 0 ? perPageRaw : this.perPage;
                        const totalItems = Number.isFinite(totalItemsRaw) && totalItemsRaw >= 0 ? totalItemsRaw : normalizedExtensions.length;
                        const inferredTotalPages = Math.max(Math.ceil(totalItems / perPage), 1);
                        const totalPages = Number.isFinite(totalPagesRaw) && totalPagesRaw >= 1
                            ? Math.max(Math.min(totalPagesRaw, inferredTotalPages || 1), 1)
                            : inferredTotalPages;
                        const currentPage = Number.isFinite(currentPageRaw) && currentPageRaw > 0
                            ? Math.min(currentPageRaw, totalPages)
                            : Math.min(this.page || 1, totalPages);

                        const resultsCount = normalizedExtensions.length;
                        const showingFrom = resultsCount ? ((currentPage - 1) * perPage) + 1 : 0;
                        const showingTo = resultsCount ? Math.min(showingFrom + resultsCount - 1, totalItems) : 0;

                        this.extensions = normalizedExtensions;
                        this.pagination = {
                            current_page: currentPage,
                            per_page: perPage,
                            total_items: totalItems,
                            total_pages: totalPages,
                            has_prev: currentPage > 1,
                            has_next: currentPage < totalPages,
                            prev_page: currentPage > 1 ? currentPage - 1 : null,
                            next_page: currentPage < totalPages ? currentPage + 1 : null,
                            showing_from: showingFrom,
                            showing_to: showingTo,
                        };

                        this.perPage = perPage;
                        this.page = currentPage;
                        this.flash = null;
                    } catch (err) {
                        console.error(err);
                        this.error = err.message || 'Unable to load extensions';
                    } finally {
                        this.loading = false;
                    }
                },
                applyFilters() {
                    this.page = 1;
                    this.fetchExtensions();
                },
                goToPage(target) {
                    if (!this.pagination) {
                        return;
                    }
                    const totalPages = this.pagination.total_pages || 1;
                    const page = Math.min(Math.max(target, 1), totalPages);
                    if (page === this.page) {
                        return;
                    }
                    this.page = page;
                    this.fetchExtensions();
                },
                prevPage() {
                    if (this.pagination?.has_prev) {
                        this.goToPage((this.pagination?.prev_page) || (this.page - 1));
                    }
                },
                nextPage() {
                    if (this.pagination?.has_next) {
                        this.goToPage((this.pagination?.next_page) || (this.page + 1));
                    }
                },
                formatDefault(value, fallback = '—') {
                    if (value === undefined || value === null || value === '') {
                        return fallback;
                    }
                    return value;
                },
                formatDate(value) {
                    if (!value) {
                        return '—';
                    }
                    const date = new Date(value);
                    if (Number.isNaN(date.getTime())) {
                        return value;
                    }
                    return date.toLocaleString();
                },
                loginAllowed(extension) {
                    return !(extension?.login_disabled);
                },
                publicCallsAllowed(extension) {
                    return !!(extension?.allow_guest_calls);
                },
                forwardingLabel(extension) {
                    const mode = (extension?.call_forwarding_mode || 'none').toLowerCase();
                    switch (mode) {
                        case 'always':
                            return 'Always';
                        case 'when_busy':
                            return 'When busy';
                        case 'when_not_answered':
                            return 'When not answered';
                        default:
                            return 'None';
                    }
                },
                forwardingDestination(extension) {
                    return extension?.call_forwarding_destination || '';
                },
                resolveDepartments(extension) {
                    const raw = extension?.departments || extension?.metadata?.departments || [];
                    if (!Array.isArray(raw)) {
                        return [];
                    }
                    return raw
                        .map((entry) => {
                            if (typeof entry === 'string') {
                                return entry;
                            }
                            if (entry && typeof entry === 'object') {
                                return entry.name || entry.display_name || entry.label || entry.title || '';
                            }
                            return '';
                        })
                        .filter(Boolean);
                },
                statusMeta(status) {
                    const normalized = (status || '').toLowerCase();
                    if (normalized === 'online') {
                        return {
                            label: 'Online',
                            badge: 'bg-emerald-50 text-emerald-600 ring-1 ring-emerald-100',
                            dot: 'bg-emerald-400',
                        };
                    }
                    if (normalized === 'ringing' || normalized === 'idle') {
                        return {
                            label: status || 'Idle',
                            badge: 'bg-amber-50 text-amber-600 ring-1 ring-amber-100',
                            dot: 'bg-amber-400',
                        };
                    }
                    return {
                        label: status || 'Unknown',
                        badge: 'bg-slate-100 text-slate-600 ring-1 ring-slate-200',
                        dot: 'bg-slate-400',
                    };
                },
                detailUrl(id) {
                    return `${this.basePath}/extensions/${id}`;
                },
                async toggleLogin(extension) {
                    if (!extension) {
                        return;
                    }
                    this.processing.toggle = extension.id;
                    this.error = null;
                    try {
                        const nextDisabled = this.loginAllowed(extension);
                        const body = new URLSearchParams();
                        body.set('login_disabled', nextDisabled ? 'true' : 'false');
                        const response = await fetch(this.detailUrl(extension.id), {
                            method: 'PATCH',
                            headers: {
                                'Accept': 'application/json',
                                'Content-Type': 'application/x-www-form-urlencoded;charset=UTF-8',
                            },
                            body: body.toString(),
                        });
                        const data = await response.json();
                        if (!response.ok) {
                            throw new Error(data?.message || 'Failed to update extension');
                        }
                        extension.login_disabled = nextDisabled;
                        this.flash = nextDisabled ? 'Login disabled for this extension.' : 'Login enabled for this extension.';
                    } catch (err) {
                        console.error(err);
                        this.error = err.message || 'Failed to toggle login state';
                        this.fetchExtensions();
                    } finally {
                        this.processing.toggle = null;
                    }
                },
                confirmDelete(extension) {
                    if (!extension) {
                        return;
                    }
                    window.dispatchEvent(new CustomEvent('console:confirm', {
                        detail: {
                            title: 'Delete extension',
                            message: `Delete extension ${extension.extension}? This action cannot be undone.`,
                            confirmLabel: 'Delete',
                            cancelLabel: 'Cancel',
                            destructive: true,
                            onConfirm: () => this.deleteExtension(extension),
                        },
                    }));
                },
                async deleteExtension(extension) {
                    this.processing.delete = extension.id;
                    this.error = null;
                    try {
                        const response = await fetch(this.detailUrl(extension.id), {
                            method: 'DELETE',
                            headers: {
                                'Accept': 'application/json',
                            },
                        });
                        const data = await response.json().catch(() => ({}));
                        if (!response.ok) {
                            throw new Error(data?.message || 'Failed to delete extension');
                        }
                        this.flash = 'Extension deleted successfully.';
                        await this.fetchExtensions();
                    } catch (err) {
                        console.error(err);
                        this.error = err.message || 'Failed to delete extension';
                    } finally {
                        this.processing.delete = null;
                    }
                },
                remainingTtl(record) {
                    if (!record || typeof record !== 'object') {
                        return 0;
                    }
                    const expires = Number(record.expires) || 0;
                    const age = Number(record.age_seconds) || 0;
                    return Math.max(expires - age, 0);
                },
                formatDuration(value) {
                    const seconds = Math.max(Number(value) || 0, 0);
                    if (seconds < 1) {
                        return '0s';
                    }
                    if (seconds < 60) {
                        return `${Math.round(seconds)}s`;
                    }
                    const minutes = Math.floor(seconds / 60);
                    const remSeconds = Math.round(seconds % 60);
                    if (minutes < 60) {
                        return remSeconds ? `${minutes}m ${remSeconds}s` : `${minutes}m`;
                    }
                    const hours = Math.floor(minutes / 60);
                    const remMinutes = minutes % 60;
                    if (hours < 24) {
                        return remMinutes ? `${hours}h ${remMinutes}m` : `${hours}h`;
                    }
                    const days = Math.floor(hours / 24);
                    const remHours = hours % 24;
                    return remHours ? `${days}d ${remHours}h` : `${days}d`;
                },
                formatRelativeSeconds(value) {
                    const seconds = Number(value);
                    if (!Number.isFinite(seconds) || seconds <= 0) {
                        return 'moments ago';
                    }
                    if (seconds < 60) {
                        return `${Math.round(seconds)}s ago`;
                    }
                    if (seconds < 3600) {
                        const minutes = Math.floor(seconds / 60);
                        const rem = Math.round(seconds % 60);
                        return rem ? `${minutes}m ${rem}s ago` : `${minutes}m ago`;
                    }
                    if (seconds < 86400) {
                        const hours = Math.floor(seconds / 3600);
                        const remMinutes = Math.floor((seconds % 3600) / 60);
                        return remMinutes ? `${hours}h ${remMinutes}m ago` : `${hours}h ago`;
                    }
                    const days = Math.floor(seconds / 86400);
                    const remHours = Math.floor((seconds % 86400) / 3600);
                    return remHours ? `${days}d ${remHours}h ago` : `${days}d ago`;
                },
                primaryRegistration(extension) {
                    const info = extension?.registrations;
                    if (!info || !info.available) {
                        return null;
                    }
                    if (!Array.isArray(info.records) || !info.records.length) {
                        return null;
                    }
                    return info.records[0];
                },
                hasActiveRegistration(extension) {
                    return !!this.primaryRegistration(extension);
                },
                registrationDevice(extension) {
                    const record = this.primaryRegistration(extension);
                    if (!record) {
                        return 'Unknown device';
                    }
                    if (record.user_agent) {
                        return record.user_agent;
                    }
                    if (record.contact) {
                        return record.contact;
                    }
                    if (record.destination) {
                        return record.destination;
                    }
                    if (record.registered_aor) {
                        return record.registered_aor;
                    }
                    return record.aor || 'Unknown device';
                },
                registrationStatus(extension) {
                    const record = this.primaryRegistration(extension);
                    if (!record) {
                        if (extension?.registered_at) {
                            return `Last seen ${this.formatDate(extension.registered_at)}`;
                        }
                        return 'Awaiting registration';
                    }
                    const ttl = this.formatDuration(this.remainingTtl(record));
                    const seen = this.formatRelativeSeconds(record.age_seconds);
                    return `${seen} • TTL ${ttl}`;
                },
                registrationError(extension) {
                    const info = extension?.registrations;
                    return info?.error || null;
                },
            };
        });
    });
</script>

{% endblock %}